// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: wsfuyibing <682805@qq.com>
// Date: 2024-11-12

package common

import (
    "context"
    "gitee.com/go-libs/log/conf"
    "net/http"
    "strconv"
    "sync"
)

type (
    // Tracing
    // 链路追踪标识接口.
    Tracing interface {
        // Child
        // 创建子链路追踪标识接口.
        //
        // 基于当前 Tracing, 创建子 Tracing 实例, 维护本节与上级的跟踪跨度ID.
        Child() Tracing

        // GetParent
        // 读取上级链路跟踪ID与点位.
        GetParent() (SpanId, int32)

        // GetParentSpanId
        // 读取上级链路跟踪ID.
        GetParentSpanId() SpanId

        // GetSpan
        // 读取链路跟踪跨度ID与点位.
        GetSpan() (SpanId, int32)

        // GetSpanAndIncrement
        // 取本级点位, 并递增至下个节点点位.
        GetSpanAndIncrement() (SpanId, int32)

        // GetSpanId
        // 读取链路跟踪跨度ID.
        GetSpanId() SpanId

        // GetTraceId
        // 读取链路追踪ID.
        GetTraceId() TraceId
    }

    // 追踪节点.
    tracing struct {
        mu                             *sync.Mutex
        spanId, parentSpanId           SpanId
        spanVersion, parentSpanVersion int32
        traceId                        TraceId
    }
)

// NewTracing
// 创建根级追踪.
func NewTracing() Tracing {
    o := (&tracing{traceId: NewTraceId()}).init()
    o.spanId = NewSpanId()
    return o
}

// NewTracingWithContext
// 创建子级追踪.
func NewTracingWithContext(ctx context.Context) Tracing {
    if v, ok := ctx.Value(ContextKey).(Tracing); ok {
        return v.Child()
    }
    return NewTracing()
}

func NewTracingWithHeader(header http.Header) Tracing {
    c := conf.New()
    if tid := header.Get(c.OpenTracingTraceId); tid != "" {
        if sid := header.Get(c.OpenTracingSpanId); sid != "" {
            o := (&tracing{}).init()
            o.traceId = NewTraceIdWithHex(tid)
            o.spanId = NewSpanId()
            o.parentSpanId, o.parentSpanVersion = NewSpanIdWithHex(sid), 0

            if pos := header.Get(c.OpenTracingSpanPosition); pos != "" {
                if n, err := strconv.ParseInt(pos, 10, 32); err == nil && n > 0 {
                    o.parentSpanVersion = int32(n)
                }
            }
            return o
        }
    }
    return NewTracing()
}

func NewTracingWithRequest(request *http.Request) Tracing {
    return NewTracingWithHeader(request.Header)
}

func (o *tracing) Child() Tracing {
    o.mu.Lock()
    defer o.mu.Unlock()

    v := (&tracing{traceId: o.traceId}).init()
    v.spanId = NewSpanId()
    v.parentSpanId, v.parentSpanVersion = o.spanId, o.spanVersion-1

    if v.parentSpanVersion < 0 {
        v.parentSpanVersion = 0
    }
    return v
}

func (o *tracing) GetParent() (SpanId, int32) {
    return o.parentSpanId, o.parentSpanVersion
}

func (o *tracing) GetParentSpanId() SpanId {
    return o.parentSpanId
}

func (o *tracing) GetSpan() (SpanId, int32) {
    o.mu.Lock()
    defer o.mu.Unlock()
    return o.spanId, o.spanVersion
}

func (o *tracing) GetSpanAndIncrement() (sid SpanId, before int32) {
    o.mu.Lock()
    defer o.mu.Unlock()
    sid = o.spanId
    before = o.spanVersion
    o.spanVersion = before + 1
    return
}

func (o *tracing) GetSpanId() SpanId { return o.spanId }

func (o *tracing) GetTraceId() TraceId { return o.traceId }

// +---------------------------------------------------------------------------+
// | Access methods                                                            |
// +---------------------------------------------------------------------------+

func (o *tracing) init() *tracing {
    o.mu = new(sync.Mutex)
    return o
}
